#include "netlib/net/tcp_client.h"

#include "netlib/base/logger.h"
#include "netlib/net/event/event_loop.h"

using namespace std::placeholders;

netlib::net::TcpClient::TcpClient(EventLoop* loop, const InetAddress& addr, std::string&& name) :
    loop_(loop), connector_(loop, addr), kName(name),
    connection_callback_([this](const TcpConnectionPtr& conn) { DefaultConnectionCallback(conn); }),
    message_callback_(DefaultMessageCallback) {
	connector_.SetNewConnectionCallback(
	    [this](auto&& PH1) { NewConnection(std::forward<decltype(PH1)>(PH1)); });
}

netlib::net::TcpClient::~TcpClient() = default;

void netlib::net::TcpClient::Connect() { // FIXME: check state
	LOG_INFO << "[" << kName << "] try to connect to " << connector_.ServerAddress().ToIpPort();
	auto res = connector_.StartUntil();
	if (res) {
		assert(connection_);
		LOG_INFO << "[" << kName << "] connected to " << connector_.ServerAddress().ToIpPort();
	} else {
		LOG_INFO << "[" << kName << "] failed to connect to "
		         << connector_.ServerAddress().ToIpPort();
	}
}

void netlib::net::TcpClient::Disconnect() {
	if (connection_) {
		connection_->Shutdown();
	}
}

void netlib::net::TcpClient::Stop() { connector_.Stop(); }

void netlib::net::TcpClient::DefaultConnectionCallback(const TcpConnectionPtr& conn) {
	LOG_INFO << Name() << ": " << conn->Name() << " [" << conn->LocalAddress().ToIpPort() << "]<->["
	         << conn->PeerAddress().ToIpPort() << "] is " << (conn->Connected() ? "UP" : "DOWN");
}

void netlib::net::TcpClient::NewConnection(int sockfd) {
	static int idx = 0;
	loop_->AssertInLoopThread();
	InetAddress peer_addr(sockets::GetPeerAddr(sockfd));
	char buf[32];
	snprintf(buf, sizeof buf, ":%s#%d", peer_addr.ToIpPort().c_str(), idx++);
	std::string conn_name = kName + buf;
	InetAddress local_addr(sockets::GetLocalAddr(sockfd));
	TcpConnectionPtr conn =
	    std::make_shared<TcpConnection>(loop_, sockfd, local_addr, peer_addr, conn_name);
	conn->SetConnectionCallback(connection_callback_);
	conn->SetMessageCallback(message_callback_);
	conn->SetWriteCompleteCallback(write_complete_callback_);
	conn->SetCloseCallback(
	    [this](auto&& PH1) { RemoveConnection(std::forward<decltype(PH1)>(PH1)); });
	connection_ = conn;
	conn->Established();
}

void netlib::net::TcpClient::RemoveConnection(const TcpConnectionPtr& conn) {
	loop_->AssertInLoopThread();
	assert(loop_ == conn->GetEventLoop());
	assert(connection_ == conn);
	connection_.reset();
	loop_->QueueInLoop([conn] { conn->Destroyed(); });

	if (IsConnected()) {
		LOG_INFO << "TcpClient::connect[" << kName << "] - Reconnecting to "
		         << connector_.ServerAddress().ToIpPort();
		connector_.Restart();
	}
}
